/*
 *  armboot - Startup Code for ARM920 CPU-core
 *
 *  Copyright (c) 2001	Marius Gr榘僥r <mag@sysgo.de>
 *  Copyright (c) 2002	Alex Z榛砶e <azu@sysgo.de>
 *  Copyright (c) 2002	Gary Jennejohn <gj@denx.de>
 *
 * See file CREDITS for list of people who contributed to this
 * project.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License as
 * published by the Free Software Foundation; either version 2 of
 * the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston,
 * MA 02111-1307 USA
 */

 /*
 *
 *  uboot的start.S,主要做的事情就是系统的各个方面的初始化。
 *  从大的方面分，可以分成这几个部分:
 *  1.设置CPU模式
 *  2.关闭看门狗
 *  3.关闭中断
 *  4.设置堆栈sp指针
 *  5.清除bss段
 *  6.异常中断处理
 *  
 */

#include <config.h>
#include <version.h>

/*
 *************************************************************************
 *
 * Jump vector table as in table 3.1 in [1]
 *
 *************************************************************************
 */


/*
*
* globl是个关键字，相当于C语言中的Extern,声明此变量，并且告诉链接器此变量是
* 全局的，外部可以访问，所以，我们可以看到uboot.lds中拥有此变量:
* ENTRY(_start),即指定入口为_start，而由下面的_start的含义可以得知，_start就是
* 整个start.S的最开始，即整个uboot的代码的开始。
*
*/

.globl _start

/*
*_start后面加上一个冒号':'，表示其是一个标号Label,类似于C语言goto后面
* 的标号。而同时，_start的值，也就是这个代码的位置了,此处即为代码的最开始
* ，相对的0的位置。而此处最开始的相对的0位置，在程序开始运行的时候，如果是
* 从NorFlash启动，那么其地址是0，
* _start=0
* 如果是重新relocate代码之后，就是我们定义的值了，即config.mk中的
* TEXT_BASE=0x33D00000
* 表示是代码段的基地址,即
* _start=TEXT_BASE=0x33D00000
*
* 而_start标号后面的:
* b     reset
* 就是跳转到对应的标号为reset的位置。
*
*/

_start:	b       reset

/*
*
* LDR指令的格式为:
* LDR{条件} 目的寄存器，<存储器地址>
* LDR指令用于从存储器中将一个32位的字数据传送到目的寄存器中。该指令通常
* 用于从存储器中读取32位的字数据到通用寄存器，然后对数据进行处理。当程序
* 计数器PC作为目的寄存器时，指令从存储器中读取的字数据被当做目的地址，从而
* 可以实现程序流程的跳转。该指令在程序设计中比较常用，且寻址方式灵活多样，请
* 认真掌握。
* 指令示例:
* LDR R0,[R1]        ;将存储器地址为R1的自数据读入寄存器R0。
* LDR R0,[R1,R2]     ;将存储器地址为R1+R2的字数据读入寄存器R0。
* LDR R0,[R1,#8]     ;将存储器地址为R1+8的字数据读入寄存器R0。
* LDR R0,[R1,R2]!    ;将存储器地址为R1+R2的字数据读入寄存器R0，并将新地址R1+R2写入R1。
* LDR R0,[R1,#8]!    ;将存储器地址为R1+8的字数据读入寄存器R0，并将新地址R1+8写入R1。
* LDR R0，[R1]，R2   ;将存储器地址为R1的字数据读入寄存器R0，并将新地址R1＋R2写入R1。
* LDR R0，[R1，R2，LSL＃2]！ ；将存储器地址为R1＋R2×4的字数据读入寄存器R0，并将
* 新地址R1＋R2×4写入R1。
* LDR R0，[R1]，R2，LSL＃2 ；将存储器地址为R1的字数据读入寄存器R0，并将新地址
* R1＋R2×4写入R1。
*
* ldr的作用，以第一个_undefined_instruction为例，就是将地址为_undefined_instruction中的
* 一个word的值，赋值给pc。
* 
* .word    .word expr {,expr}... 分配一段字内存单元，并用expr初始化字内存单元(32bit)
* 
* 所以，下面的含义是:以_undefined_instruction为例，就是，此处分配了一个word=32bit=4字节
* 的地址空间，里面存放的值是undefined_instruction。
* 
* 而此处_undefined_instruction也就是该地址空间的地址了。用C语言来表达就是:
* _undefined_instruction = &undefined_instruction
* 或 
* *_undefined_instruction = undefined_instruction
*
*/
	ldr	pc, _undefined_instruction
	ldr	pc, _software_interrupt
	ldr	pc, _prefetch_abort
	ldr	pc, _data_abort
	ldr	pc, _not_used
	ldr	pc, _irq
	ldr	pc, _fiq

_undefined_instruction:	.word undefined_instruction
_software_interrupt:	.word software_interrupt
_prefetch_abort:	.word prefetch_abort
_data_abort:		.word data_abort
_not_used:		.word not_used
_irq:			.word irq
_fiq:			.word fiq

/*
* balignl的语法格式为:
* 意思是，接下来的代码，都要16字节对齐，不足之处，用0xdeadbeef填充。 
* 其中关于所要填充的内容0xdeadbeef，其实就是没啥真正的意思，不过此处
* 为何是0xdeadbeef，是作者故意搞笑的，写成deadbeef，告诉读代码的人，这里
* 是坏死的牛肉，所以，类似的，我们也可以故意改为goodbeef。表示good beef，好
* 的牛肉。
*
*/
	.balignl 16,0xdeadbeef

/*
 *************************************************************************
 * first:
 * Startup Code (reset vector)
 *
 * do important init only if we don't start from memory!
 * relocate armboot to ram
 * setup stack
 * jump to second stage
 *
 *************************************************************************
 */

/*
*
* 此处和上面的类似，_TEXT_BASE是一个标号地址，此地址中是一个word类型的变量，
* 变量名是TEXT_BASE，此值见名知意，是text的base，即代码的基地址。可以在config.mk
* 中找到其定义:
* TEXT_BASE = 0X33D00000
* 
*/
_TEXT_BASE:
	.word	TEXT_BASE

/*
*
* 同理，此含义可以用c语言表示: 
* *(_armboot_start) = _start
*
*
*/
.globl _armboot_start
_armboot_start:
	.word _start

/*
 * These are defined in the board-specific linker script.
 *
 * 关于_bss_start,_bss_end都只是两个标号，对应着此处的地址。
 * 而两个地址里面分别存放的值是__bss_start和_end，这两个的值
 * 根据注释所说，是定义在开发板相关的链接脚本里面的，我们此处
 * 的开发板相关的链接脚本是: u-boot.lds
 * 
 *
 *
 */
.globl _bss_start
_bss_start:
	.word __bss_start

.globl _bss_end
_bss_end:
	.word _end

/*
* 
* 关于FREE_RAM_END和FREE_RAM_SIZE，这里只是两个标号，之所以也是声明为全局变量，
* 是因为uboot的源码中会用到这两个变量。
* 
* 但是这里有点特别的是，这两个变量，将在本源码start.S中的后面要用到，而在后面用到这
* 两个变量之前，uboot的C源码中，会先去修改这两个值，具体的逻辑是:
*
*/
.globl FREE_RAM_END
FREE_RAM_END:
	.word	0x0badc0de

.globl FREE_RAM_SIZE
FREE_RAM_SIZE:
	.word	0x0badc0de

/*
* 
* 同上，IRQ_STACK_START和FIQ_STACK_START，也是在cpu_init中用到了。
* 不过此处，是只有当定义了宏CONFIG_USE_IRQ的时候，才用到这两个变量，其含义也很明显，
* 只有用到了中断IRQ，才会用到中断的堆栈，才有中断堆栈的起始地址。快速中断FIQ，同理。
*
*/
#ifdef CONFIG_USE_IRQ
/* IRQ stack memory (calculated at run-time) */
.globl IRQ_STACK_START
IRQ_STACK_START:
	.word	0x0badc0de

/* IRQ stack memory (calculated at run-time) */
.globl FIQ_STACK_START
FIQ_STACK_START:
	.word 0x0badc0de
#endif

/*
 * the actual reset code
 * CPSR:当前的程序状态寄存器(Current Program Status Register),
 * SPSR:保存的程序状态寄存器(Saved Program Status Register),
 * MRS:Move From Status Register
 * MRS指令格式为: 
 * MRS{条件}  通用寄存器，程序状态寄存器(CPSR或SPSR)
 * MRS指令用于将程序状态寄存器的内容传送到通用寄存器中。该指令一般用在以下两种情况:
 * 1.当需要改变程序状态寄存器的内容时，可用MRS将程序状态寄存器的内容读入通用寄存器，
 * 修改后再写回程序状态寄存器。
 *
 * 2.当在异常处理或进程切换时，需要保存程序状态寄存器的值，可先用该指令读出程序状态
 * 寄存器的值，然后保存。
 * 指令示例:
 * MRS R0,CPSR          ;传送CPSR的内容到R0
 * MRS R0,SPSR          ;传送SPSR的内容到R0
 *
 */

reset:
	/*
	 * set the cpu to SVC32 mode
	 */
	/*传送cpsr的内容到r0*/
	mrs	r0,cpsr

    /*
    *
    * bic指令的语法格式是:
    * BIC{条件}{S} 目的寄存器，操作数1，操作数2
    * BIC指令用于清除操作数1的某些位，并把结果放置到目的寄存器中。操作数1应是一个寄存器，
    * 操作数2可以是一个寄存器，被移位的寄存器，或一个立即数。操作数2为32位的掩码，如果在
    * 如果在掩码中设置了某一位，则清除这一位。未设置的掩码位保持不变。
    * 而0x1f=11111b
    * 所以，此行代码的含义就是，清除r0的bit[4:0]位。
    *
    */
	bic	r0,r0,#0x1f

    /*
    *
    * orr的指令语法格式为:
    * ORR{条件}{S} 目的寄存器，操作数1，操作数2
    * ORR指令用于在两个操作数上进行逻辑或运算，并把结果放置到目的寄存器中。操作数1应是
    * 一个寄存器，操作数2可以是一个寄存器，被移位的寄存器，或一个立即数。该指令常用于设置
    * 操作数1的某些位。
    * 指令示例:ORR R0，R0，#3       ;该指令设置R0的0、1位，其余位保持不变。
    * 所以此行汇编代码的含义为: 
    * oxd3 = 1101 0111(10011:svc模式)
    * 将r0与0xd3算数或运算，然后将结果给r0。
    *
    */
	orr	r0,r0,#0xd3

    /*
    * MSR:程序状态寄存器访问指令
    * MSR{条件}  程序状态寄存器(CPSR或SPSR)_<域>，操作数
    * MSR指令用于将操作数的内容传送到程序状态寄存器的特定域中。其中，操作数可以为通用寄存器
    * 或立即数。<域>用于设置程序状态寄存器中需要操作的位，32位的程序状态寄存器可分为4个域:
    * 位[31:24]为条件标志位域，用f表示；
    * 位[23:16]为状态位域，用s表示；
    * 位[15:8]为扩展位域，用x表示；
    * 位[7:0]为控制位域，用c表示;
    * 该指令通常用于恢复或改变程序状态寄存器的内容，在使用时，一般要在MSR指令中指明将要操作的域。
    * MSR CPSR,R0       ;传送R0的内容到CPSR
    * MSR SPSR,R0       ;传送R0的内容到SPSR
    * MSR CPSR_c,R0     ;传送R0的内容到SPSR，但仅仅修改CPSR中的控制位域    
    * 此行汇编的含义为，将r0的值赋给CPSR。
    *
    */
	msr	cpsr,r0

/*second*/
/* turn off the watchdog */
#if defined(CONFIG_S3C2400)
# define pWTCON		0x15300000
# define INTMSK		0x14400008	/* Interupt-Controller base addresses */
# define CLKDIVN	0x14800014	/* clock divisor register */
#elif defined(CONFIG_S3C2410) || defined(CONFIG_S3C2440)
# define pWTCON		0x53000000  /* Watchdog Timer Mode Of 2440 datasheet */
# define INTMOD		0X4A000004  /* Interrupt mode regiseter:0 = IRQ mode   1 = FIQ mode */
# define INTMSK		0x4A000008	/* Interupt-Controller base addresses */
# define INTSUBMSK	0x4A00001C
# define CLKDIVN	0x4C000014	/* clock divisor register */
#endif

#if defined(CONFIG_S3C2400) || defined(CONFIG_S3C2410) || defined(CONFIG_S3C2440)
    /*
     * 这里的ldr是之前的ldr不是一个意思，这里的ldr是伪指令ldr
     * 伪指令，就是伪的指令，是针对真的指令而言的。   
     * 真的指令就是那些常见的指令，比如上面说的arm的ldr，bic，msr等等指令，是arm
     * 体系结构中真正存在的指令，你在arm汇编指令集中找得到对应的含义。
     * 而伪指令是写出来给汇编程序看的，汇编程序能看的伪指令具体表示的是啥意思，
     * 然后将其翻译成真正的指令或者进行相应的处理。
     *
     * 虽然ldr伪指令和ARM的ldr指令很想，但是作用不太一样。ldr伪指令可以在立即数前
     * 加上=，以表示把一个地址写到某寄存器中，比如:
     * ldr r0, =0x12345678
     * 这样，就把0x12345678这个地址写到r0中了。所以，ldr伪指令和mov是比较相似的。 
     * 只不过mov指令后面的立即数是有限制的，这个立即数，能够必须由一个8位的二进制数，即
     * 属于0x00 - 0xFF内的某个值，经过偶数次右移后得到，这样才是合法数据，而ldr伪指令没有
     * 这个限制。
     * 那为何ldr伪指令的操作数没有限制，那是因为其是伪指令，写出来的伪指令，最终会被编译器
     * 解释成为真正的，合法的指令的，一般都是对应的mov指令。
     *   
     * 这里的意思就是把pWTCON宏定义的值赋给r0,即:
     * r0=0x53000000 
     *
     */    
	ldr     r0, =pWTCON

    /*
    * 
    * mov指令语法:
    *   MOV{条件}{S} 目的寄存器，源操作数 
    * MOV指令可完成从另一个寄存器、被移位的寄存器或将一个立即数加载到目的寄存器。其中S选项  
    * 决定指令的操作是否影响CPSR中条件标志位的值，当没有S时指令不更新CPSR中条件标志位的值。
    * 指令示例: 
    * MOV R1,R0             ;将寄存器R0的值传送到寄存器R1
    * MOV PC,R14            ;将寄存器R14的值传送到PC，常用于子程序返回
    * MOV R1,R0,LSL#3       ;将寄存器R0的值左移3位后传送到R1
    * 不过对MOV指令来说,可以用:
    * MOV R0,R0
    * 的指令来实现NOP(目的就是等待CPU一个指令周期)操作。
    * 下面这mov指令的含义为:就是把0x0赋值给r1，即r1=0x0
    *
    */
	mov     r1, #0x0

    /*
     * str指令格式为:
     * STR{条件} 源寄存器,<存储器地址>
     * STR指令用于从源寄存器中将一个32位的字数据传送到存储器中。该指令在程序设计中
     * 比较常用，且寻址方式灵活多样，使用方式可参考指令LDR。
     * 指令示例:
     * STR R0,[R1],#8       ;将R0的字数据写入以R1为地址的存储器中，并将新地址R1+8写入R1。
     * STR R0,[R1,#8]       ;将R0中的字数据写入以R1+8为地址的存储器中。
     * 下面这话的含义为:将r1寄存器的值，传送到地址值为r0的(存储器)内存中。
     * 用C语言表示就是:
     * *r0 = r1
     *
     * 总结:
     * 上面几行代码的意思就是:
     * 先用r0寄存器存pWTCON的值，然后r1=0，再将r1中的0写入到pWTCON中，其实就是
     * pWTCON = 0;
     * 根据watchdog各个位的含义可知，这些代码的作用就是关闭看门狗的复位功能。
     * 
     */
	str     r1, [r0]

	 /* third */
    /*
	 * mask all IRQs by setting all bits in the INTMR - default
	 * 下面代码的意思是:就是将INTMSK寄存器设置为0xffffffff，即，
	 * 将所有的中断都mask了。
	 *
	 */
	mov	r1, #0xffffffff
	ldr	r0, =INTMSK
	str	r1, [r0]


    /*
     * 0x7fff表示bit[14:0]上全是1，表示全部中断都被屏蔽
     *
     */
# if defined(CONFIG_S3C2410)
	ldr	r1, =0x3ff
	ldr	r0, =INTSUBMSK
	str	r1, [r0]
# elif defined(CONFIG_S3C2440)
	ldr	r1, =0x7fff
	ldr	r0, =INTSUBMSK
	str	r1, [r0]
# endif

    /*
     * CLKDIVN的具体含义为:
     * 
     *
     *
     *
     */
#if 0
	/* FCLK:HCLK:PCLK = 1:2:4 */
	/* default FCLK is 120 MHz ! */
	ldr	r0, =CLKDIVN
	mov	r1, #3
	str	r1, [r0]
#endif
#endif	/* CONFIG_S3C2400 || CONFIG_S3C2410 || CONFIG_S3C2440 */

	/*
	 * we do sys-critical inits only at reboot,
	 * not when booting from ram!
	 *
	 * 关于bl指令的含义:
	 * b指令，是单纯的跳转指令，即CPU直接跳转到某地址继续执行。
	 * 而BL是Branch with Link，带分支的跳转，而Link指的是Link Register，链接寄存器R14，即lr，
	 * 所以，bl的含义是，除了包含b指令的跳转功能,在跳转之前，还把r15寄存器=PC=cpu地址，赋值给
	 * r14=lr，然后跳转到对应位置等要做的事情执行完毕之后，再用:
	 * mov pc,lr
	 * 使得cpu再跳转回来，所以整个逻辑就是调用子程序的意思。
	 *
	 * bl的语法为:
	 * BL{条件} 目标地址 
	 * BL是另一个跳转指令，但跳转之前，会在寄存器R14中保存PC的当前内容，因此，可以通过将R14
	 * 的内容重新加载PC中，来返回到跳转指令之后的那个指令处执行。该指令是实现子程序调用的一个
	 * 基本但常用的手段。以下指令:
	 * BL  Label    ;当程序无条件跳转到标号Label处执行时，同时将当前的PC值保存到R14中。
	 * 所以下面的代码的含义为:调用子程序cpu_init_crit,等cpu_init_crit执行完毕，再返回
	 * 此处继续执行下面的代码。
	 * 对于对应的b指令，就只是单纯的跳转到某处继续执行，而不能再通过mov pc,lr跳转回来了。
	 *
	 */
#ifndef CONFIG_SKIP_LOWLEVEL_INIT
	bl	cpu_init_crit
#endif

	/* 
	 * four: 设置堆栈sp指针
     *
	 * Set up the stack
	 *
	 */
stack_setup:
	ldr	r0, _TEXT_BASE		/* upper 128 KiB: relocated uboot  _TEXT_BASE = 0x33D00000  */

    /*
     * SUB指令:
     * SUB{条件}{S} <dest>,<op_1>,<op_2> 
     * dest = op_1 - op_2
     * 
     * SUB用操作数one减去操作数two，把结果放置到目的寄存器中。操作数1是一个寄存器，
     * 操作数2可以是一个寄存器，被移位的寄存器，或一个立即值:
     * SUB   R0,R1,R2       ;R0 = R1 - R2 
     * SUB   R0,R1,#256     ;R0 = R1 - 256
     * SUB   R0,R2,R3,LSL#1 ;R0 = R2 - (R3 << 1)
     * 减法可以在有符号和无符号数上进行。
     *
     * 所以下面的含义为:r0 = 0x33D00000 - 0x40000(CFG_MALLOC_LEN) - 0x80(CFG_GBL_DATA_SIZE) = 0x33CBFF80
     *
     */ 
	sub	r0, r0, #CFG_MALLOC_LEN	/* 256k:malloc area    r0 = r0 - #CFG_MALLOC_LEN                  */
	sub	r0, r0, #CFG_GBL_DATA_SIZE /*128byte:bdinfo      r0 = r0 - #CFG_GBL_DATA_SIZE               */

    /*
     * 如果定义了CONFIG_USE_IRQ，即如果使用中断的话，那么再把r0的值减去IRQ+FIQ之和。
     * 即:0x33CBFF80 - 8k(0x2000) = 0x33CBDF80
     *
     */
#ifdef CONFIG_USE_IRQ
	sub	r0, r0, #(CONFIG_STACKSIZE_IRQ+CONFIG_STACKSIZE_FIQ)        //4k,4k
#endif

    /*
     * 最后，再减去终止异常的堆栈大小，即12个字节
     * 现在r0的值为:0x33CBDF80 - 0xC = 0x33CBDF74
     * 然后把r0的值赋值给sp指针，
     * 关于:
     * sp:代表stack pointer，堆栈指针
     * ip:代表instruction pointer，指令指针。
     *
     */
	sub	sp, r0, #12		/* leave 3 words for abort-stack    */

   /*
    * init the clock:初始化时钟
    * 
    *
    */
	bl clock_init

    /*
     * ADR伪指令:小范围的地址读取
     * ADR伪指令将基于PC相对偏移的地址值或基于寄存器相对偏移的地址值读取到寄存器中。 
     * 在汇编编译器编译源程序时，ADR伪指令被编译器换成一条合适的指令。通常，编译器用
     * 一条ADD指令或者SUB指令来实现该ADR伪指令的功能，若不能用一条指令实现，则产生错误，
     * 编译失败。
     * ADR伪指令格式:ADR{cond} register,expr
     * 地址表达式expr的取值范围:
     *   当地址值是字节对齐时，其取值范围为:+255 ~ 255B;
     *   当地址值是字对齐时，其取值范围为:-1020 ~1020B;
<<<<<<< Updated upstream
<<<<<<< HEAD
=======
     * 所以下面的adr r0，_start语句的意思为:   
     *  就是将_start的地址复制给r0，
     *
     *
     *
>>>>>>> aaf3d0ca2c54cf2947b91929e019accaa7bd0571
=======
     *  所以下面的adr r0，_start语句的意思为:   
     *  就是将_start的地址复制给r0，一般在arm五级(三级)流水中，需要PC = PC + 8(原理请
     *  参考相关资料)。
     *  PC不是指向你正在运行的指令，
     *  而是PC始终指向你要取的指令的地址。
     *  PC（execute）=PC（fetch）+ 8
     *  其中：
     *  PC（fetch）：当前正在执行的指令，就是之前取该指令时候的 PC 的值。
     *  PC（execute）：当前指令执行的计算中，如果用到 PC，则此时 PC 的值。
     *
     *  对应地，在 ARM7 的三级流水线（取指，译指，执行）和 ARM9 的五级流水线（取指，译
     *  指，执行，存储，写回）中，可以返么说：
     *  PC，  总是指向当前正在被取指的指令的地址，
     *  PC-4，总是指向当前正在被译指的指令的地址，
     *  PC-8，总是指向当前的那条指令，即我们一般说的，正在被执行的指令的地址。
>>>>>>> Stashed changes
     *
     *  根本的原因是，两者的流水线设计中，指令的 Execute 执行阶段，都是处亍流水线的第三
     *  级。所以使得 PC=PC+8。
     *  类似地，可以推导出：
     *  假设，Execute 阶段处亍流水线中的第 E 阶段，每条指令是 T 个字节，那么
     *  PC = PC + N*T
     *  PC = PC + (E - 1) * T
     * 【 关于直接改变 PC 的值，会导致流水线清空的解释】
     *  把 PC 的值直接赋值为 0x20。而 PC 值更改，直接导致流水线的清空，即导致下一个 cycle
     *  中的，对应的流水线中的其他几个步骤，包括接下来的同一个 Cycle 中的取指的工作被取消。
     *  在 PC 跳转到 0x20 的位置乊后，流水线重新计算，重新一步步地按照流水线的逻辑，去一
     *  点点执行。当然要保证当前指令的执行完成，即执行乊后，迓有两个 cycle，分别做的
     *  Memory 和 Write，会继续执行完成。
     *
     *
     *  MOV合法的取值范围:可以通过0x00-0xFF中某个数，循环右移偶数位而产生，
     *  就是合法的mov的操作数，否则就是非法的mov的操作数。

     * 【总结】
     *  adr r0, _start
     *  的伪代码，被翻译成实际汇编代码为：
     *  33d000a4: e24f00ac sub r0, pc, #172 ; 0xac
     *  其含义就是，通过计算PC+8-172 => _start的地址，
     *  而_start的地址，即相对代码段的0地址，是这个地址在运行时刻的值，而当ARM920T加电启动
     *  后，，此处是从Nor Flash启动，对应的代码，也是在Nor Flash中，对应的物理地址是0x0,所
     *  以，此时_start的值就是0，而不是0x33d00000。
     *  所以，此时：r0 = 0x0
     *  
     */

#ifndef CONFIG_SKIP_RELOCATE_UBOOT
relocate:				/* relocate U-Boot to RAM	    */
	adr	r0, _start		/* r0 <- current position of code   */

    /* r1 = 0x33D00000 */
	ldr	r1, _TEXT_BASE		/* test if we run from flash or RAM */

    /*
    *  
    * 返两句很简单，就是比较r0和r1。而
    * r0 = 0x0
    * r1 = 0x33D00000
    * 所以不相等，因此beq发现两者不相等，就不会去跳转到clear_bss，不会去执行对应的将bss段
    * 清零的动作了。
    *
    */
	cmp     r0, r1                  /* don't reloc during debug         */
	beq     clear_bss

   /*
    * 
    *  返两行代码意思也很清楚，分别装载_armboot_start和_bss_start地址中的值，赋值给r2和r3.
    *  
    *
    */
	ldr	r2, _armboot_start
	ldr	r3, _bss_start

   /*
    *  而此处的意思就很清楚了，就是r2 = r3-r2，计算出
    *  text + rodata + data
    *  的大小，即整个需要载入的数据量是多少，用亍下面的凼数去拷贝返么多的数据到对应的内存的
    *  位置。
    *【总结】
    * 到此刻位置,假定是从Nor Flash吭劢的话：
    * r0 = 0x0 = 我们代码此刻所在的位置
    * r1 = 0x33D00000 = 我们想要把我们的代码放到哪里
    * r2 = 0x000339d4?? = 对应的代码的大小（此处的代码 = text + rodata + data）
    *  
    */
	sub	r2, r3, r2		/* r2 <- size of armboot            */

    /*
    *
    * 调用CopyCode2Ram函数,CopyCode2Ram函数参数分别为(unsigned long start_addr, unsigned char *buf, int size)
    * 跟上面提到的r0，r1，r2对应起来，r0代表source，r1代表dst，r2代表size，具体的对应关系也是在APCS中
    * 定义的。
    *
    * 实际参数
    * APCS 没有定义记录、数组、和类似的格局。这样语言可以自由的定义如何进行这些活动。但
    * 是，如果你自己的实现实际上不符合 APCS 的精神，那么将不允许来自你的编译器的代码与来
    * 自其他编译器的代码连接在一起。典型的，使用 C 语言的惯例。
    * *前 4 个整数实参(或者更少!)被装载到 a1 - a4。
    * *前 4 个浮点实参(或者更少!)被装载到 f0 - f3。
    * *其他任何实参(如果有的话)存储在内存中，用迕入凼数时紧接在 sp 的值上面的字来指
    * *向。换句话说，其余的参数被压入栈顶。所以要想简单。最好定义接受 4 个戒更少的
    * *参数的函数。
    *
    */
#if 1
	bl  CopyCode2Ram		/* r0: source, r1: dest, r2: size */
#else
	add	r2, r0, r2		/* r2 <- source end address         */

copy_loop:
	ldmia	r0!, {r3-r10}		/* copy from source address [r0]    */
	stmia	r1!, {r3-r10}		/* copy to   target address [r1]    */
	cmp	r0, r2			/* until source end addreee [r2]    */
	ble	copy_loop
#endif
#endif	/* CONFIG_SKIP_RELOCATE_UBOOT */

/* 清除bss段 */
clear_bss:
	ldr	r0, _bss_start		/* find start of bss segment        */
	ldr	r1, _bss_end		/* stop here                        */
	mov 	r2, #0x00000000		/* clear                            */

/*
 * 而此段代码，含义也很清晰，那就是，
 * 先将r2,即0x0，存到地址为r0的内存中去，然后r0地址加上4，比较r0地址和r1地址，即比较当
 * 前地址是否到了bss段的结束位置，如果le，little or equal，小亍戒等亍，那么就跳到clbss_l,
 * 即接着返几个步骤，直到地址超过了bss的_end位置，即实现了将整个bss段，都清零。 
 *
 */
clbss_l:str	r2, [r0]		/* clear loop...                    */
	add	r0, r0, #4
	cmp	r0, r1
	ble	clbss_l

#if 0
	/* try doing this stuff after the relocation */
	ldr     r0, =pWTCON
	mov     r1, #0x0
	str     r1, [r0]

	/*
	 * mask all IRQs by setting all bits in the INTMR - default
	 */
	mov	r1, #0xffffffff
	ldr	r0, =INTMR
	str	r1, [r0]

	/* FCLK:HCLK:PCLK = 1:2:4 */
	/* default FCLK is 120 MHz ! */
	ldr	r0, =CLKDIVN
	mov	r1, #3
	str	r1, [r0]
	/* END stuff after relocation */
#endif

    /*
    * 只看最后的那两行，意思也很简单，那就是将地址为_start_armboot中的内容，即
    * start_armboot，赋值给PC，即调用start_armboot函数。
    * 至此，汇编语言的start.S的整个工作，就完成了。
    *
    */
	ldr	pc, _start_armboot

_start_armboot:	.word start_armboot

/*
 *************************************************************************
 *
 * CPU_init_critical registers
 *
 * setup important registers
 * setup memory timing
 *
 *************************************************************************
 */

/*
 * ARM微处理器可支持多达16个协处理器，用于各种协处理器操作，在程序执行的过程中，每个
 * 协处理器只执行针对自身的协处理指令，忽略ARM处理器和其他协处理器的指令。ARM的协处理器
 * 指令主要用于ARM处理器初始化ARM协处理器的数据处理操作，以及在ARM处理器的寄存器和协处理器
 * 的寄存器之间传送数据，和在ARM协处理器的寄存器和存储器之间传送数据。ARM协处理器指令包括以
 * 下5条:
 * CDP协处理器数操作指令
 * LDC协处理器数据加载指令
 * STC协处理器数据存储指令
 * MCR ARM处理器寄存器到协处理器寄存器的数据传送指令
 * MRC 协处理器寄存器到ARM处理器寄存器的数据传送指令
 *
 * CP15系统控制协处理器:
 * CP15一系统控制协处理器(the system control coprocessor) 他通过协处理器指令MCR和
 * MRC提供具体的寄存器来配置和控制caches、MMU、保护系统、配置时钟模式(在bootloader
 * 时钟初始化用到)
 * CP15的寄存器只能被MRC和MCR指令访问。
 * CP15有很多个寄存器，分别叫做寄存器0(Register 0)，到寄存器15（Register 15），
 * 每个寄存器分别控制不同的功能，而且有的是只读，有的是只写，有的是可读写。
 * 而且这些寄存器的含义，随着版本ARM内核版本变化而不断扩展。
 * 
 * MCR指令将ARM处理器的寄存器中的数据传送到协处理器寄存器中。如果协处理器不能成功地
 * 执行该操作，将产生未定义的指令异常中断。
 * 指令语法格式
 * MCR{<cond>} <p>，< opcode_1>，<Rd>,<CRn>,<CRm>{,<opcode_2>}
 * MCR{<cond>} p15，0，<Rd>,<CRn>,<CRm>{,<opcode_2>}
 * 其中，<cond>为指令执行的条件码。当<cond>忽略时指令为无条件执行。
 * < opcode_1>为协处理器将执行的操作的操作码。对于CP15协处理器来说，< opcode_1>永
 * 迖为0b000，当< opcode_1>不为0b000时，该指令操作结果不可预知。
 * <Rd>作为源寄存器的ARM寄存器，其值将被传送到协处理器寄存器中。
 * <CRn>作为目标寄存器的协处理器寄存器，其编号可能是C0，C1，…，C15。
 * <CRm>和<opcode_2>两者组合决定对协处理器寄存器进行所需要的操作，如果没有指定，
 * 则将为<CRm>为C0，opcode_2为0。
 *
 */ 

#ifndef CONFIG_SKIP_LOWLEVEL_INIT
cpu_init_crit:
	/*
	 * flush v4 I/D caches
	 */
	mov	r0, #0

    /*
     * 下面语句根据上面定义，可以知道含义为:清空指令缓存I Cache和数据缓存D Cache
     */
	mcr	p15, 0, r0, c7, c7, 0	/* flush v3/v4 cache */

     /*
     * 下面语句根据上面定义，可以知道含义为:清空TLB的指令缓存I Cache和数据缓存D Cache
     */
	mcr	p15, 0, r0, c8, c7, 0	/* flush v4 TLB */

	/*
	 * disable MMU stuff and cache
	 */

    /*
     * 关闭mmu
     */
	mrc	p15, 0, r0, c1, c0, 0

    /*
     * 下面几行代码表示:去清除对应的位和设置对应的位
     */
	bic	r0, r0, #0x00002300	@ clear bits 13, 9:8 (--V- --RS)
	bic	r0, r0, #0x00000087	@ clear bits 7, 2:0 (B--- -CAM)
	orr	r0, r0, #0x00000002	@ set bit 2 (A) Align
	orr	r0, r0, #0x00001000	@ set bit 12 (I) I-Cache

    /*
     * 将刚才设置的数据再写入到寄存器1中
     */
	mcr	p15, 0, r0, c1, c0, 0

	/*
	 * before relocating, we have to setup RAM timing
	 * because memory timing is board-dependend, you will
	 * find a lowlevel_init.S in your board directory.
	 */

    /*
     * 将lr的值赋值给ip，此处为什么要保存lr，是因为此处是在子函数"cpu_init_crit"中，
     * lr已经保存了待会用于返回主函数的地址，即上次调用时候的pc的值，而此处如果在子
     * 函数cpu_init_crit中继续调用其他子函数lowlevel_init，而不保存lr的话，那么调用
     * 完lowlevel_init返回来的时候，就丢失了cpu_init_crit要返回的位置。
     * 说白了就是，每次你要调用函数之前，你自己要确保是否已经正确保存了lr的值，要保证 
     * 函数调用完毕后，也能正常返回。当然，如果你此处根本不需要返回，那么就不用去保存lr
     * 的值了。
     *
     *
     */ 
	mov	ip, lr

    /*
    * 跳转到lowlevel_init接口执行
    */
	bl	lowlevel_init

    /*
    * 把ip的值赋值给lr链接寄存器
    */
	mov	lr, ip

    /*
     * 等cpu_init_crit执行完毕再跳转回原来的地方继续执行
     *
     * 是典型的子函数调用，通过将lr的值赋值给pc，实现函数调用完成后而返回的。
     */
     */
	 mov	pc, lr
#endif /* CONFIG_SKIP_LOWLEVEL_INIT */

/*
 *************************************************************************
 *
 * Interrupt handling(异常中断处理)
 *
 *************************************************************************
 */

@
@ IRQ stack frame.
@
#define S_FRAME_SIZE	72

#define S_OLD_R0	68
#define S_PSR		64
#define S_PC		60
#define S_LR		56
#define S_SP		52

#define S_IP		48
#define S_FP		44
#define S_R10		40
#define S_R9		36
#define S_R8		32
#define S_R7		28
#define S_R6		24
#define S_R5		20
#define S_R4		16
#define S_R3		12
#define S_R2		8
#define S_R1		4
#define S_R0		0

#define MODE_SVC 0x13
#define I_BIT	 0x80

/*
 * use bad_save_user_regs for abort/prefetch/undef/swi ...
 * use irq_save_user_regs / irq_restore_user_regs for IRQ/FIQ handling
 */

    /*
    * .macro和后面的.endm相对应，此处相对于一个没有参数的宏，
    * 也就相当于一个函数了。
    *
    */
	.macro	bad_save_user_regs

    /*
    *  
    * S_FRAME_SIZE = 72
    *
    * sp = sp - 72
    *
    */
	sub	sp, sp, #S_FRAME_SIZE

    /*
    *
    * stmia语法为:
    * <LDM|STM>{cond}<FD|ED|FA|EA|IA|IB|DA|DB> Rn{!},<Rlist>{^} 
    * 更具体的含义:
    * 批量数据加载/存储指令ARM微处理器所支持批量数据加载/存储指令可以一次 
    * 在一片连续的存储器单元和多个寄存器之间传送数据，批量加载指令用于将一
    * 片连续的存储器中的数据传送到多个寄存器，批量数据存储指令则完成相反的
    * 操作。常用的加载存储指令如下:
    * LDM(或STM)指令:
    * LDM(或STM)指令的格式为:
    * LDM(或STM){条件} 基址寄存器{!},寄存器列表{^}
    * LDM（或STM）指令用于从由基址寄存器所指示的一片连续存储器到寄存器列表所指示的多个
    * 寄存器之间传送数据，该指令的常见用途是将多个寄存器的内容入栈或出栈。其中，{类型}为以
    * 下几种情况： 
    * IA  每次传送后地址加1;
    * IB  每次传送前地址加1;
    * DA  每次传送后地址减1; 
    * DB  每次传送前地址减1;
    * FD  满递减堆栈; 
    * ED  空递增堆栈;
    * FA  满递增堆栈;
    * EA  空递增堆栈;
    * {!}为可选后缀，若选用该后缀，则当数据传送完毕之后，将最后的地址写入基址寄存器，否则
    * 基址寄存器的内容不改变。
    *
    * 基址寄存器不允许为R15，寄存器列表可以为R0～R15的任意组合。
    * {∧}为可选后缀，当指令为LDM且寄存器列表中包含R15，选用该后缀时表示：除了正常的数据
    * 传送之外，还将SPSR复制到CPSR。同时，该后缀还表示传入或传出的是用户模式下的寄存器，
    * 而不是当前模式下的寄存器。
    * 指令示例：
    * STMFD R13!，{R0，R4-R12，LR} ；将寄存器列表中的寄存器（R0，R4到
    * R12，LR）存入堆栈。
    * LDMFD R13!，{R0，R4-R12，PC} ；将堆栈内容恢复到寄存器（R0，R4到
    * R12，LR)。
    *
    * 所以，此行的含义是，
    * 将r0到r12的值，一个个地传送到对应的地址上，基地址是sp的值，传完一个，sp的值加4，一
    * 直到传送完为止。
    *
    */
	stmia	sp, {r0 - r12}			@ Calling r0-r12

    /*
    * 
    * 将_armboot_start的值赋值给r2,即为_start的值
    * 而_start的值：
    * 从 Nor Flash启动时：_stat=0
    * relocate代码之后为：_start=TEXT_BASE=0x33D00000
    * 此处是已经relocate代码了，所以应该理解为后者，即_start=0x33D00000
    * 
    */
	ldr	r2, _armboot_start

    /*
    * r2 = r2 - #(CONFIG_STACKSIZE(128k)+CFG_MALLOC_LEN(256K))
    * r2
    * = r2 - ( CONFIG_STACKSIZE+CFG_MALLOC_LEN)
    * = r2 – (128*1024 + 256*1024)
    * = 0x33D00000 - 384KB
    * = 0x33CA0000
    *
    */
	sub	r2, r2, #(CONFIG_STACKSIZE+CFG_MALLOC_LEN)

    /*
    * r2 = r2 - 138bytes = 0x33CA0000 - 138 = 0x33C9FF78
    *
    */
	sub	r2, r2, #(CFG_GBL_DATA_SIZE+8)  @ set base 2 words into abort stack

    /*
    * 分别将地址为r2和r2+4的内容，即地址为 0x33C9FF78 和 0x33C9FF7C 中的内容，load载
    * 入给r2和r3寄存器。
    */
	ldmia	r2, {r2 - r3}			@ get pc, cpsr

    /*
    * r0 = sp + S_FRAME_SIZE(72)
    */
	add	r0, sp, #S_FRAME_SIZE		@ restore sp_SVC

    /*
    * r5 = sp + #S_SP(52)
    */
	add	r5, sp, #S_SP

    /*
    * 将lr给r1
    */
	mov	r1, lr

    /*
    * 将r0到r3中的内容，存储到地址为r5-r5+12中的位置去。
    */
	stmia	r5, {r0 - r3}			@ save sp_SVC, lr_SVC, pc, cpsr

    /*
    * sp 赋值给 r0
    */
	mov	r0, sp

    /*
    * 结束宏bad_save_user_regs
    */
	.endm

    /*
    * 
    */
	.macro	irq_save_user_regs
	sub	sp, sp, #S_FRAME_SIZE
	stmia	sp, {r0 - r12}			@ Calling r0-r12
	add     r8, sp, #S_PC
	stmdb   r8, {sp, lr}^                   @ Calling SP, LR
	str     lr, [r8, #0]                    @ Save calling PC
	mrs     r6, spsr
	str     r6, [r8, #4]                    @ Save CPSR
	str     r0, [r8, #8]                    @ Save OLD_R0
	mov	r0, sp
	.endm

	.macro	irq_restore_user_regs
	ldmia	sp, {r0 - lr}^			@ Calling r0 - lr
	mov	r0, r0
	ldr	lr, [sp, #S_PC]			@ Get PC
	add	sp, sp, #S_FRAME_SIZE
	subs	pc, lr, #4			@ return & move spsr_svc into cpsr
	.endm

    /*
    * 此处代码是:在出错的时候，获得对应堆栈的值
    */
	.macro get_bad_stack
	ldr	r13, _armboot_start		@ setup our mode stack
	sub	r13, r13, #(CONFIG_STACKSIZE+CFG_MALLOC_LEN)
	sub	r13, r13, #(CFG_GBL_DATA_SIZE+8) @ reserved a couple spots in abort stack

	str	lr, [r13]			@ save caller lr / spsr
	mrs	lr, spsr
	str     lr, [r13, #4]

	mov	r13, #MODE_SVC			@ prepare SVC-Mode
	@ msr	spsr_c, r13
	msr	spsr, r13
	mov	lr, pc
	movs	pc, lr
	.endm

    /*
    * 把地址为IRQ_STACK_START中的值赋值给sp，即获得IRQ_STACK_START堆栈的起始地址
    */
	.macro get_irq_stack			@ setup IRQ stack
	ldr	sp, IRQ_STACK_START
	.endm

    /*
    * 把地址为FIQ_STACK_START中的值赋值给sp，即获得FIQ_STACK_START堆栈的起始地址
    */
	.macro get_fiq_stack			@ setup FIQ stack
	ldr	sp, FIQ_STACK_START
	.endm

/*
 * exception handlers
 *
 *
 * 这里的undefined_instruction作为一个标号，即为一个地址值，对应着就是在发生"未定义指令"
 * 的时候，系统索要去执行的代码。
 * (其他几个对应的"软件中断"，"预取指错误"，"数据错误","未定义","(普通)中断","快速中断",
 *  也是同样的做法，跳转到对应的位置执行对应的代码。)
 *
 *
 */

    /*
    *
    * 如果发生未定义指令异常，CPU会
    * 掉转到start.S开头中对应的位置：
    * ldr pc, _undefined_instruction
    * 即把地址为_undefined_instruction中的内容给pc，即跳转到此处执行对应的代码。
    * 其做的事情依次是：
    * *获得出错时候的堆栈
    * *保存用户模式寄存器
    * *跳转到对应的函数：do_undefined_instruction
    *
    *
    */
	.align  5
undefined_instruction:
	get_bad_stack
	bad_save_user_regs
	bl 	do_undefined_instruction

	.align	5
software_interrupt:
	get_bad_stack
	bad_save_user_regs
	bl 	do_software_interrupt

	.align	5
prefetch_abort:
	get_bad_stack
	bad_save_user_regs
	bl 	do_prefetch_abort

	.align	5
data_abort:
	get_bad_stack
	bad_save_user_regs
	bl 	do_data_abort

	.align	5
not_used:
	get_bad_stack
	bad_save_user_regs
	bl 	do_not_used

/*
 *  此处相当于一个叫做Launch的函数，做了也是类似的系统初始化的动作。
 *  但是没找到此函数在哪里被调用的。具体不太清楚。
 *
 */
@ HJ
.globl Launch
    .align	4
Launch:    
    mov r7, r0
    @ diable interrupt
	@ disable watch dog timer
	mov	r1, #0x53000000
	mov	r2, #0x0
	str	r2, [r1]

    ldr r1,=INTMSK
    ldr r2,=0xffffffff  @ all interrupt disable
    str r2,[r1]

    ldr r1,=INTSUBMSK
    ldr r2,=0x7ff       @ all sub interrupt disable
    str r2,[r1]

    ldr     r1, = INTMOD
    mov r2, #0x0        @ set all interrupt as IRQ (not FIQ)
    str     r2, [r1]

    @ 
	mov	ip, #0
	mcr	p15, 0, ip, c13, c0, 0      @	/* zero PID */
	mcr	p15, 0, ip, c7, c7, 0       @	/* invalidate I,D caches */
	mcr	p15, 0, ip, c7, c10, 4      @	/* drain write buffer */
	mcr	p15, 0, ip, c8, c7, 0       @	/* invalidate I,D TLBs */
	mrc	p15, 0, ip, c1, c0, 0       @	/* get control register */
	bic	ip, ip, #0x0001             @	/* disable MMU */
	mcr	p15, 0, ip, c1, c0, 0       @	/* write control register */

    @ MMU_EnableICache
    @mrc p15,0,r1,c1,c0,0
    @orr r1,r1,#(1<<12)
    @mcr p15,0,r1,c1,c0,0

#ifdef CONFIG_SURPORT_WINCE
    bl Wince_Port_Init
#endif

    @ clear SDRAM: the end of free mem(has wince on it now) to the end of SDRAM
    ldr     r3, FREE_RAM_END
    ldr     r4, =PHYS_SDRAM_1+PHYS_SDRAM_1_SIZE    @ must clear all the memory unused to zero
    mov     r5, #0

    ldr     r1, _armboot_start
    ldr     r2, =On_Steppingstone
    sub     r2, r2, r1
    mov     pc, r2
On_Steppingstone:
2:  stmia   r3!, {r5}
    cmp     r3, r4
    bne     2b

    @ set sp = 0 on sys mode
    mov sp, #0

    @ add by HJ, switch to SVC mode
	msr	cpsr_c,	#0xdf	@ set the I-bit = 1, diable the IRQ interrupt
	msr	cpsr_c,	#0xd3	@ set the I-bit = 1, diable the IRQ interrupt
    ldr sp, =0x31ff5800	
    
    nop
	nop
    nop
	nop

	mov     pc, r7  @ Jump to PhysicalAddress
	nop
    mov pc, lr

/*
 * 此处，做的事情，很容易看懂，就是中断发生后，跳转到返里，然后
 * 保存对应寄存器，然后跳转到对应irq函数IRQ_Handle中去。
 * 但是前面为何sp为何去减去4，原因不太懂
 *
 */
#ifdef CONFIG_USE_IRQ

	.align	5
irq:
/* add by www.embedsky.net to use IRQ for USB and DMA */
	sub	lr, lr, #4			        @ the return address
	ldr	sp, IRQ_STACK_START	        @ the stack for irq
	stmdb	sp!, 	{ r0-r12,lr }	@ save registers
	
	ldr	lr,	=int_return		        @ set the return addr
	ldr	pc, =IRQ_Handle		        @ call the isr
int_return:
	ldmia	sp!, 	{ r0-r12,pc }^	@ return from interrupt

/*
 * 此处也很简单，就是发生了快速中断FIQ的时候，保存IRQ的用户模式寄存器，然后调用凼数
 * do_fiq,调用完毕后，再恢复IRQ的用户模式寄存器。
 */
	.align	5
fiq:
	get_fiq_stack
	/* someone ought to write a more effiction fiq_save_user_regs */
	irq_save_user_regs
	bl 	do_fiq
	irq_restore_user_regs
    
/*
 * 此处就是，如果没有定义CONFIG_USE_IRQ，那么就用这段代码，可以看到，都只是直接调用
 * do_irq和do_fiq，也没做什么实际工作。
 */
#else

	.align	5
irq:
	get_bad_stack
	bad_save_user_regs
	bl 	do_irq

	.align	5
fiq:
	get_bad_stack
	bad_save_user_regs
	bl 	do_fiq

#endif